// file: kernel/task/schedule.c
// autor: jiang xinpeng
// time:2021.2.11
// copyright:(C) 2020-2050 by jiangxinpeng,All right are reserved.

#include <arch/interrupt.h>
#include <arch/cpu.h>
#include <arch/fpu.h>
#include <arch/task.h>
#include <os/schedule.h>
#include <os/exception.h>
#include <os/task.h>
#include <os/memcache.h>
#include <lib/type.h>
#include <lib/assert.h>

volatile task_t *sched_next = NULL;
volatile uint8_t sched_flags = 0;

// schedule manager
schedule_t *schedule = NULL;

// init schedule manager
void ScheduleInit()
{
    schedule = KMemAlloc(sizeof(schedule_t));
    schedule->tasknum = 0;
    SpinLockInit(&schedule->lock);
    schedule->cpunum = cpunum;

    if(cpunum>CPU_NUM_MAX)
    {
        Panic("schedule: cpu nums %d above system limit : CPU_NUM_MAX %d\n",cpunum,CPU_NUM_MAX);
    }

    // init all cpu unit
    for (int i = 0; i < schedule->cpunum; i++)
        ScheduleUnitInit(schedule->sched_unit_table + i, cpu_list[i], 0);

    KPrint("[schedule] schedule unit init ok!\n");
}

void ScheduleUnitInit(sched_unit_t *unit, cpuid_t cpuid, int flags)
{
    sched_queue_t *queue;

    unit->cpuid = cpuid;
    unit->flags = flags;
    unit->cur = NULL;
    unit->idle = NULL;
    unit->tasknum = 0;
    unit->dynamic_proiority = 0;
    SpinLockInit(&unit->lock);
    // init priority queue
    for (int i = 0; i < TASK_PRIOR_LEVEL_MAX; i++)
    {
        queue = &unit->priority_queue[i];
        queue->priority = i;
        queue->len = 0;
        list_init(&queue->list);
        SpinLockInit(&queue->lock);
    }

    KPrint("[su] init unit id %d\n", unit->cpuid);
}

// calc task new priority
// according adjustment
uint8_t SchedCalcNewPriority(task_t *task, char adjustment)
{
    char priority = task->priority;

    assert((priority <= TASK_PRIOR_LEVEL_REALTIME));

    if (priority < TASK_PRIOR_LEVEL_REALTIME)
    {
        priority += adjustment;

        if (priority >= TASK_PRIOR_LEVEL_REALTIME)
            priority = TASK_PRIOR_LEVEL_REALTIME - 1;

        if (priority < task->static_priority)
            priority = task->static_priority;
    }
    return priority;
}

int SchedQueueHadTask(sched_unit_t *su, task_t *task)
{
    sched_queue_t *queue = &su->priority_queue[su->dynamic_proiority];
    return list_find(&task->list, &queue->list);
}

int SchedQueueAddTaskTail(sched_unit_t *su, task_t *task)
{
    sched_queue_t *queue;
    uint32_t eflags;
    
    SpinLockDisInterruptSave(&su->lock,eflags);
    // task priority above su dynamic priority
    if (task->priority > su->dynamic_proiority)
    {
        su->dynamic_proiority = task->priority;
    }

    // add to targe su priority queue
    queue = &su->priority_queue[task->priority];
    if (!list_is_head(&task->list))
        list_init(&task->list);
    list_add_tail(&task->list, &queue->list);
    task->cpuid = su->cpuid;
    task->last_cpuid = CpuGetMyId();
    queue->len++;
    su->tasknum++;
    schedule->tasknum++;
    // KPrint("cur task %s add to prior %d\n", task->name, task->priority);
    SpinUnlockEnableInterruptRestore(&su->lock,eflags);
}

int SchedQueueRemoveTask(sched_unit_t *su, task_t *task)
{
    sched_queue_t *queue;
    task_t *cur = NULL;
    uint8_t found = 0;
    int i;
    uint32_t eflags;

    SpinLockDisInterruptSave(&su->lock,eflags);
    for (i = 0; i < 4; i++)
    {
        queue = &su->priority_queue[i];
        list_traversal_all_owner_to_next(cur, &queue->list, list)
        {
            if (cur->pid == task->pid)
            {
                found = 1;
                break;
            }
        }
    }
    if (found)
    {
        list_del_init(&task->list);
        task->cpuid = -1;
        task->last_cpuid = su->cpuid;
        queue->len--;
        su->tasknum--;
        schedule->tasknum--;
    }
    SpinUnlockEnableInterruptRestore(&su->lock,eflags);
    return 0;
}

int SchedQueueAddHead(sched_unit_t *su, task_t *task)
{
    sched_queue_t *queue;

    if (SchedQueueHadTask(su, task)) // task had been add to schedule queue
    {
        return 0;
    }

    uint32_t eflags;
    SpinLockDisInterruptSave(&su->lock,eflags);

    // task priority above su dynamic priority
    if (task->priority > su->dynamic_proiority)
    {
        su->dynamic_proiority = task->priority;
    }
    // add to targe su priority queue
    queue == &su->priority_queue[su->dynamic_proiority];

    list_add_head(&task->list, &queue->list);
    task->cpuid = su->cpuid;
    queue->len++;
    su->tasknum++;
    schedule->tasknum++;

    SpinUnlockEnableInterruptRestore(&su->lock,eflags);
    return 0;
}

sched_unit_t *SchedGetCurUnit()
{
    sched_unit_t *su = NULL;
    cpuid_t cpuid = CpuGetMyId();

    for (int i = 0; i < schedule->cpunum; i++)
    {
        su = &schedule->sched_unit_table[i];
        if (su->cpuid == cpuid)
        {
            return su;
        }
    }
    KPrint("[su] no found cur unit!\n");
    return NULL;
}

// doing task schedule
void Schedule()
{
    sched_unit_t *su = SchedGetCurUnit();
    if (!su)
        return;

    task_t *cur = cur_task;
    task_t *next = SchedGetNextTask(su);

    if (!next) // current unit no tasks
    {
#ifdef ENABLE_SMP
        // TaskBalancePull(); // pull some tasks from other cpus
#endif
        if (!su->idle)
            return;
        next = su->idle;
    }
    SchedSetNextTask(su,next);
    TaskSwitchToNext(cur, next);
}

void SchedSetNextTask(sched_unit_t *su, task_t *next)
{
    FpuSave(&cur_task->fpu);
    su->cur = next;
    TaskActive(next);
    FpuRestore(&next->fpu);
}

task_t *SchedGetNextTask(sched_unit_t *su)
{
    if (!su)
        return NULL;

    task_t *task = su->cur;
    task_t *next = NULL;
    
    switch (task->status)
    {
    case TASK_RUNNING:
        task->ticks = task->timeslice;
        task->status = TASK_READY;
    case TASK_READY:
        // no real-time task are dynamic priority
        if (task->priority < TASK_PRIOR_LEVEL_REALTIME && task->priority >= TASK_PRIOR_LEVEL_LOW)
        {
            task->priority--;
            if (task->priority < TASK_PRIOR_LEVEL_LOW)
            {
                task->priority = task->static_priority;
            }
        }
        SchedQueueAddTaskTail(su, task);
        break;
    default:
        break;
    }
    // get next task
    next = SchedQueueFetchFirst(su);
    return next;
}

task_t *SchedQueueFetchFirst(sched_unit_t *su)
{
    sched_queue_t *queue = NULL;
    task_t *task;

    // fetch a high priority queue
    // if queue is empty,fetch next priority
    while ((!su->priority_queue[su->dynamic_proiority].len) && su->dynamic_proiority>0)
    {
        --su->dynamic_proiority;
    }

    queue = &su->priority_queue[su->dynamic_proiority];
    task = list_first_owner(&queue->list, task_t, list);
    if (task != NULL)
    {
        list_del(&task->list);
        queue->len--;
        su->tasknum--;
        schedule->tasknum--;
        return task;
    }

    return NULL;
}

sched_unit_t *SchedGetFreeUnit()
{
    int i;
    sched_unit_t *su = NULL;

    for (i = 0; i < CPU_NUM_MAX; i++)
    {
        su = &schedule->sched_unit_table[i];
        if (su->tasknum < TASK_NUM_HIGH)
        {
            return su;
        }
    }
    return SchedGetCurUnit();
}

sched_unit_t *SchedFindByCpu(cpuid_t cpu)
{
    sched_unit_t *su = schedule->sched_unit_table;
    int i;
    for (i = 0; i < CPU_NUM_MAX; i++)
    {
        su = &schedule->sched_unit_table[i];
        if (su->cpuid == cpu)
            return su;
    }
    return su;
}
